package edu.washington.cs.workflow.analysis;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.ipa.callgraph.CallGraph;
import com.ibm.wala.ipa.callgraph.propagation.LocalPointerKey;
import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis;
import com.ibm.wala.ipa.callgraph.propagation.PointerKey;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SSAInvokeInstruction;

import edu.washington.cs.workflow.util.Utils;
import edu.washington.cs.workflow.util.WALAUtils;

public class AccessedGUIObjectFinder {

	public final CallGraph cg;
	public final PointerAnalysis pa;
	public final String[] appPackages;
	
	private GUILabelIdentifier identifier = new GUILabelIdentifier();
	
	public AccessedGUIObjectFinder(AnalysisBuilder builder) {
		this(builder.getCallGraph(), builder.getPointerAnalysis(), builder.appPackages);
	}
	
	public AccessedGUIObjectFinder(CallGraph cg, PointerAnalysis pa, String[] appPackages) {
		Utils.checkNotNull(cg);
		Utils.checkNotNull(pa);
		Utils.checkNotNull(appPackages);
		this.cg = cg;
		this.pa = pa;
		this.appPackages = appPackages;
	}
	
	public List<GUIObjectInitInfo> findGUIObjects(String methodSignature) {
		CGNode node = WALAUtils.lookupCGNode(cg, methodSignature);
		Utils.checkNotNull(node, "Method not found: " + methodSignature);
		
		//first get all reachable app nodes. do not consider call back, and library call
		List<CGNode> appNodes = WALAUtils.getReachableAppNodes(node, cg, appPackages); //this.getReachableAppNodes(node);
		
		Utils.debugPrintln("reachable app nodes: " + appNodes.size()
				+ "  " + appNodes);
		
		List<GUIObjectInitInfo> retObjs = new LinkedList<GUIObjectInitInfo>();
		for(CGNode appNode : appNodes) {
			for(SSAInstruction ssa : WALAUtils.getAllIRs(appNode)) {
				//just for invocation
				if(ssa instanceof SSAInvokeInstruction) {
					SSAInvokeInstruction invoke = (SSAInvokeInstruction)ssa;
					if(!this.identifier.isGUIValueGettingCall(invoke)) {
						continue;
					}
//					System.out.println(invoke + "##" + invoke.getCallSite().getDeclaredTarget().getName().toString());
					//is a value getting call
					Utils.checkTrue(!invoke.isStatic());
					int guiObjValueNum = invoke.getReceiver(); //XXX should be OK
//					System.out.println("    " + guiObjValueNum);
					//XXX wrong here, may need to consult the points-to info
					Collection<GUIObjectInitInfo> objs = findGUIObjectInfo(appNode, guiObjValueNum);
					retObjs.addAll(objs);
				}
			}
		}
		
		return retObjs;	
	}
	
	private Collection<GUIObjectInitInfo> findGUIObjectInfo(CGNode node, int valueNum) {
		List<GUIObjectInitInfo> objList = new LinkedList<GUIObjectInitInfo>();
		
		Utils.debugPrintln(WALAUtils.getAllIRAsString(node));
		
		LocalPointerKey lpKey = this.findLocalPointerKey(node, valueNum, this.pa);
		Utils.debugPrintln("local pointer key: " + lpKey);
		if(lpKey != null) {
			Collection<PointerKey> keys = WALAUtils.findAlias(this.pa, lpKey);
			Utils.debugPrintln("number of alias: " + keys.size());
			Collection<LocalPointerKey> localKeys = new LinkedHashSet<LocalPointerKey>();
			for(PointerKey key : keys) {
				if(key instanceof LocalPointerKey) {
					LocalPointerKey lpkey = (LocalPointerKey)key;
					String pkg = WALAUtils.getJavaPackageName(lpkey.getNode().getMethod().getDeclaringClass());
					if(Utils.startWith(pkg, this.appPackages)) {
					    localKeys.add(lpkey);
					}
				}
			}
//			System.out.println("   " + localKeys.size() + ",  " + localKeys);
			
			for(LocalPointerKey localKey : localKeys) {
				Collection<GUIObjectInitInfo> objs = this.findGUIObjectInfoLocally(localKey.getNode(), localKey.getValueNumber());
				objList.addAll(objs);
			}
		}
		
		return objList;
	}
	
	private Collection<GUIObjectInitInfo> findGUIObjectInfoLocally(CGNode node, int valueNum) {
		List<GUIObjectInitInfo> objList = new LinkedList<GUIObjectInitInfo>();
		List<SSAInstruction> irList = WALAUtils.getAllIRs(node);
		for(SSAInstruction ssa : irList) {
			if(!(ssa instanceof SSAInvokeInstruction)) {
				continue;
			}
			if(!GUIObjectInitFinder.useVarInInstruction(valueNum, ssa)) {
				continue;
			}
			SSAInvokeInstruction labelSetInstr = (SSAInvokeInstruction)ssa;
			if(!this.identifier.isLabelSettingMethod(labelSetInstr)) {
				continue;
			}
			//make sure the correct var
			if(this.identifier.getGUIIndexInLabelSetting(labelSetInstr) == valueNum) {
				int labelVarIndex = this.identifier.getLabelVarIndex(labelSetInstr);
				String label = this.identifier.getLabelForGUIObject(node, labelSetInstr, labelVarIndex);
				//create the gui object info
				String typeName = GUIObjectInitFinder.getReceiverObjTypeName(labelSetInstr);
				GUIObjectInitInfo objInfo = new GUIObjectInitInfo(label, typeName , node);
				objList.add(objInfo);
			}
		}
		
		return objList;
	}
	
	/**
	 * Find the local key
	 * */
	public LocalPointerKey findLocalPointerKey(CGNode node, int valueNum, PointerAnalysis pa) {
		for(PointerKey key : pa.getPointerKeys()) {
			if(key instanceof LocalPointerKey) {
				LocalPointerKey lpKey = (LocalPointerKey)key;
				if(lpKey.getNode().equals(node) && lpKey.getValueNumber() == valueNum) {
					return lpKey;
				}
			}
		}
		
		return null;
	}
}